﻿using System;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Core;
using MugenInjection.Core.Components;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using Should;

namespace MugenInjectionTest.Converters
{
    [TestClass]
    public class ManagedWrapperConverterTest : ConverterTestBase<InjectorWrapperConverter>
    {
        [TestMethod]
        public void ManagedScopeWrapperTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                var converter = CreateNew();
                injector.Components.BindingActivatorComponent.AddConverter(converter);
                injector.Components.BindingActivatorComponent.AddConverter(new FactoryAndLazyConverter());

                injector.Bind<ManagedDisposable>().ToSelf().InManagedScope().TryDisposeObjects();
                var managed = injector.Get<Managed<ManagedDisposable>>();
                AssertManagedScope(managed);
            }
        }

        [TestMethod]
        public void ManagedScopeWrapperIntoTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                var converter = CreateNew();
                injector.Components.BindingActivatorComponent.AddConverter(converter);
                injector.Components.BindingActivatorComponent.AddConverter(new FactoryAndLazyConverter());

                injector.Bind<ManagedDisposable>().ToSelf().InManagedScope().TryDisposeObjects();
                var managedWrapperTest = injector.Get<ManagedWrapperTest>();
                AssertManagedScope(managedWrapperTest.Managed);
                AssertManagedScope(managedWrapperTest.ManagedLazy.Value);
                AssertManagedScope(managedWrapperTest.ManagedFunc());
            }
        }

        [TestMethod]
        public void CustomManagedScopeWrapperTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                var converter = CreateNew();
                injector.Components.BindingActivatorComponent.AddConverter(converter);
                injector.Components.BindingActivatorComponent.AddConverter(new FactoryAndLazyConverter());
                converter.InjectorWrapperTypes[0] = typeof(CustomManaged<>);

                injector.Bind<ManagedDisposable>().ToSelf().InManagedScope().TryDisposeObjects();
                var managed = injector.Get<CustomManaged<ManagedDisposable>>();
                AssertManagedScope(managed);
            }
        }

        [TestMethod]
        public void CustomManagedScopeWrapperIntoTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                var converter = CreateNew();
                injector.Components.BindingActivatorComponent.AddConverter(converter);
                injector.Components.BindingActivatorComponent.AddConverter(new FactoryAndLazyConverter());
                converter.InjectorWrapperTypes[0] = typeof(CustomManaged<>);

                injector.Bind<ManagedDisposable>().ToSelf().InManagedScope().TryDisposeObjects();
                var managedWrapperTest = injector.Get<CustomManagedWrapperTest>();
                AssertManagedScope(managedWrapperTest.Managed);
                AssertManagedScope(managedWrapperTest.ManagedLazy.Value);
                AssertManagedScope(managedWrapperTest.ManagedFunc());
            }
        }

        [TestMethod]
        public void ManagedCacheClearTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                var converter = CreateNew();
                injector.Components.BindingActivatorComponent.AddConverter(converter);
                injector.Components.BindingActivatorComponent.AddConverter(new FactoryAndLazyConverter());

                injector.Bind<ManagedDisposable>().ToSelf().InManagedScope().TryDisposeObjects();
                var managedWrapperTest = injector.Get<Managed<ManagedDisposable>>();
                AssertManagedScope(managedWrapperTest);

                converter.InjectorWrapperTypes.Clear();
                TestUtils.Catch<BindingNotFoundException>(() => managedWrapperTest = injector.Get<Managed<ManagedDisposable>>());
            }
        }




        [TestMethod]
        public void UoWScopeWrapperTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                var converter = CreateNew();
                injector.Components.BindingActivatorComponent.AddConverter(converter);
                injector.Components.BindingActivatorComponent.AddConverter(new FactoryAndLazyConverter());

                injector.Bind<ManagedDisposable>().ToSelf().InUnitOfWorkScope().TryDisposeObjects();
                var uoW = injector.Get<UoW<ManagedDisposable>>();
                AssertUoWScope(uoW);
            }
        }

        [TestMethod]
        public void ManagedUoWWrapperIntoTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                var converter = CreateNew();
                injector.Components.BindingActivatorComponent.AddConverter(converter);
                injector.Components.BindingActivatorComponent.AddConverter(new FactoryAndLazyConverter());

                injector.Bind<ManagedDisposable>().ToSelf().InUnitOfWorkScope().TryDisposeObjects();
                var managedWrapperTest = injector.Get<UoWWrapperTest>();
                AssertUoWScope(managedWrapperTest.Managed);
                AssertUoWScope(managedWrapperTest.ManagedLazy.Value);
                AssertUoWScope(managedWrapperTest.ManagedFunc());
            }
        }

        [TestMethod]
        public void CustomUoWScopeWrapperTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                var converter = CreateNew();
                injector.Components.BindingActivatorComponent.AddConverter(converter);
                injector.Components.BindingActivatorComponent.AddConverter(new FactoryAndLazyConverter());
                converter.InjectorWrapperTypes[1] = typeof(CustomUoW<>);

                injector.Bind<ManagedDisposable>().ToSelf().InUnitOfWorkScope().TryDisposeObjects();
                var managed = injector.Get<CustomUoW<ManagedDisposable>>();
                AssertUoWScope(managed);
            }
        }

        [TestMethod]
        public void CustomUoWScopeWrapperIntoTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                var converter = CreateNew();
                injector.Components.BindingActivatorComponent.AddConverter(converter);
                injector.Components.BindingActivatorComponent.AddConverter(new FactoryAndLazyConverter());
                converter.InjectorWrapperTypes[1] = typeof(CustomUoW<>);

                injector.Bind<ManagedDisposable>().ToSelf().InUnitOfWorkScope().TryDisposeObjects();
                var managedWrapperTest = injector.Get<CustomUoWWrapperTest>();
                AssertUoWScope(managedWrapperTest.Managed);
                AssertUoWScope(managedWrapperTest.ManagedLazy.Value);
                AssertUoWScope(managedWrapperTest.ManagedFunc());
            }
        }

        [TestMethod]
        public void UoWCacheClearTest()
        {
            using (MugenInjector injector = GetInjector())
            {
                injector.Components.BindingActivatorComponent.RemoveAllConverters();
                var converter = CreateNew();
                injector.Components.BindingActivatorComponent.AddConverter(converter);
                injector.Components.BindingActivatorComponent.AddConverter(new FactoryAndLazyConverter());

                injector.Bind<ManagedDisposable>().ToSelf().InUnitOfWorkScope().TryDisposeObjects();
                var managedWrapperTest = injector.Get<UoW<ManagedDisposable>>();
                AssertUoWScope(managedWrapperTest);

                converter.InjectorWrapperTypes.Clear();
                TestUtils.Catch<BindingNotFoundException>(() => managedWrapperTest = injector.Get<UoW<ManagedDisposable>>());
            }
        }




        private static void AssertManagedScope(Managed<ManagedDisposable> managed)
        {
            managed.IsValueCreated.ShouldBeFalse();
            managed.Value.IsDisposed.ShouldBeFalse();
            managed.Dispose();
            managed.Value.IsDisposed.ShouldBeTrue();
        }

        private static void AssertManagedScope(CustomManaged<ManagedDisposable> managed)
        {
            managed.IsValueCreated.ShouldBeFalse();
            managed.Value.IsDisposed.ShouldBeFalse();
            managed.Dispose();
            managed.Value.IsDisposed.ShouldBeTrue();
        }

        private static void AssertUoWScope(UoW<ManagedDisposable> managed)
        {
            managed.IsValueCreated.ShouldBeFalse();
            managed.Value.IsDisposed.ShouldBeFalse();
            managed.Dispose();
            managed.Value.IsDisposed.ShouldBeTrue();
        }

        private static void AssertUoWScope(CustomUoW<ManagedDisposable> managed)
        {
            managed.IsValueCreated.ShouldBeFalse();
            managed.Value.IsDisposed.ShouldBeFalse();
            managed.Dispose();
            managed.Value.IsDisposed.ShouldBeTrue();
        }

        #region Nested type: ManagedWrapperTest

        public class ManagedDisposable : DisposableObject
        {

        }

        public class CustomManagedWrapperTest
        {
            public CustomManaged<ManagedDisposable> Managed { get; set; }
            public Lazy<CustomManaged<ManagedDisposable>> ManagedLazy { get; set; }
            public Func<CustomManaged<ManagedDisposable>> ManagedFunc { get; set; }

            /// <summary>
            /// Initializes a new instance of the <see cref="T:System.Object"/> class.
            /// </summary>
            public CustomManagedWrapperTest(CustomManaged<ManagedDisposable> managed, Lazy<CustomManaged<ManagedDisposable>> managedLazy, Func<CustomManaged<ManagedDisposable>> managedFunc)
            {
                Managed = managed;
                ManagedLazy = managedLazy;
                ManagedFunc = managedFunc;
            }
        }

        public class ManagedWrapperTest
        {
            public Managed<ManagedDisposable> Managed { get; set; }
            public Lazy<Managed<ManagedDisposable>> ManagedLazy { get; set; }
            public Func<Managed<ManagedDisposable>> ManagedFunc { get; set; }

            /// <summary>
            /// Initializes a new instance of the <see cref="T:System.Object"/> class.
            /// </summary>
            public ManagedWrapperTest(Managed<ManagedDisposable> managed, Lazy<Managed<ManagedDisposable>> managedLazy, Func<Managed<ManagedDisposable>> managedFunc)
            {
                Managed = managed;
                ManagedLazy = managedLazy;
                ManagedFunc = managedFunc;
            }
        }

        #endregion

        #region Nested type: UoWWrapperTest

        public class CustomUoWWrapperTest
        {
            public CustomUoW<ManagedDisposable> Managed { get; set; }
            public Lazy<CustomUoW<ManagedDisposable>> ManagedLazy { get; set; }
            public Func<CustomUoW<ManagedDisposable>> ManagedFunc { get; set; }

            /// <summary>
            /// Initializes a new instance of the <see cref="T:System.Object"/> class.
            /// </summary>
            public CustomUoWWrapperTest(CustomUoW<ManagedDisposable> managed, Lazy<CustomUoW<ManagedDisposable>> managedLazy, Func<CustomUoW<ManagedDisposable>> managedFunc)
            {
                Managed = managed;
                ManagedLazy = managedLazy;
                ManagedFunc = managedFunc;
            }
        }

        public class UoWWrapperTest
        {
            public UoW<ManagedDisposable> Managed { get; set; }
            public Lazy<UoW<ManagedDisposable>> ManagedLazy { get; set; }
            public Func<UoW<ManagedDisposable>> ManagedFunc { get; set; }

            /// <summary>
            /// Initializes a new instance of the <see cref="T:System.Object"/> class.
            /// </summary>
            public UoWWrapperTest(UoW<ManagedDisposable> managed, Lazy<UoW<ManagedDisposable>> managedLazy, Func<UoW<ManagedDisposable>> managedFunc)
            {
                Managed = managed;
                ManagedLazy = managedLazy;
                ManagedFunc = managedFunc;
            }
        }

        #endregion

        #region Nested type: CustomManaged

        /// <summary>
        /// Represents a dependency that can be released by the dependent component.
        /// </summary>
        /// <typeparam name="T">The service provided by the dependency.</typeparam>
        public class CustomManaged<T> : DisposableObject
        {
            #region Fields

            private readonly IBindingContext _bindingContext;
            private readonly object _locker;
            private readonly IManagedScope _managedScope;
            private T _value;

            #endregion

            #region Constructors

            /// <summary>
            ///     Initializes a new instance of the <see cref="CustomManaged{T}" /> class.
            /// </summary>
            public CustomManaged(IInjector injector, IBindingContext bindingContext)
            {
                Validate.ArgumentNotNull(injector, "injector");
                Validate.ArgumentNotNull(bindingContext, "bindingContext");
                _managedScope = injector.CreateManagedScope();
                _bindingContext = bindingContext;
                _locker = new object();
            }

            #endregion

            #region Properties

            /// <summary>
            ///     Gets a value that indicates whether a value has been created for this <see cref="Managed{T}" /> instance.
            /// </summary>
            public bool IsValueCreated { get; set; }

            /// <summary>
            ///     Gets the lazily initialized value of the current <see cref="Managed{T}" /> instance.
            /// </summary>
            public T Value
            {
                get
                {
                    if (IsValueCreated)
                        return _value;
                    lock (_locker)
                    {
                        if (IsValueCreated)
                            return _value;
                        _value = (T)_managedScope.Resolve(_bindingContext);
                        IsValueCreated = true;
                    }
                    return _value;
                }
            }

            #endregion

            #region Overrides of DisposableObject

            /// <summary>
            /// Releases resources held by the object.
            /// </summary>
            protected override void Dispose(bool disposing)
            {
                if (disposing)
                    _managedScope.Dispose();
                base.Dispose(disposing);
            }

            #endregion
        }

        #endregion

        #region Nested type: CustomUoW

        /// <summary>
        ///     Represents a dependency that can be released by the dependent component.
        /// </summary>
        /// <typeparam name="T">The service provided by the dependency.</typeparam>
        public sealed class CustomUoW<T> : DisposableObject
        {
            #region Fields

            private readonly IBindingContext _bindingContext;
            private readonly IInjector _injector;
            private readonly object _locker;
            private T _value;

            #endregion

            #region Constructors

            /// <summary>
            ///     Initializes a new instance of the <see cref="CustomUoW{T}" /> class.
            /// </summary>
            public CustomUoW(IInjector injector, IBindingContext bindingContext)
            {
                Validate.ArgumentNotNull(injector, "injector");
                Validate.ArgumentNotNull(bindingContext, "bindingContext");
                _injector = injector.CreateChild(injector.Settings.Clone(),
                                                 new ComponentContainer(), true,
                                                 InjectorUtilsInternal.EmptyComponents);
                _bindingContext = bindingContext;
                _locker = new object();
            }

            #endregion

            #region Properties

            /// <summary>
            ///     Gets a value that indicates whether a value has been created for this <see cref="Managed{T}" /> instance.
            /// </summary>
            public bool IsValueCreated { get; set; }

            /// <summary>
            ///     Gets the lazily initialized value of the current <see cref="Managed{T}" /> instance.
            /// </summary>
            public T Value
            {
                get
                {
                    if (IsValueCreated)
                        return _value;
                    lock (_locker)
                    {
                        if (IsValueCreated)
                            return _value;
                        _value = (T)_injector.Resolve(_bindingContext);
                        IsValueCreated = true;
                    }
                    return _value;
                }
            }

            #endregion

            #region Overrides of DisposableObject

            /// <summary>
            ///     Releases resources held by the object.
            /// </summary>
            protected override void Dispose(bool disposing)
            {
                if (disposing)
                    _injector.Dispose();
                base.Dispose(disposing);
            }

            #endregion
        }

        #endregion

        #region Overrides of ConverterTestBase<InjectorWrapperConverter>

        protected override InjectorWrapperConverter CreateNew()
        {
            return new InjectorWrapperConverter();
        }

        #endregion
    }
}
